BigQuery Migration Service の SQL 変換機能が一般提供になりました
ウィスキー、シガー、パイプをこよなく愛する大栗です。
BigQuery への移行をサポートする BigQuery Migration Service が昨年 10 月に SQL を変換する機能がプレビューとして公開されていましたが、このたび一般提供となりました。これと主に SQL オブジェクトの名称マッピングやメタデータを抽出するツールもプレビューとして公開されたのでレポートしています。
- BigQuery release notes June 08, 2022
- Accelerating BigQuery migrations with automated SQL translation
BigQuery Migration Service
BigQuery Migration Service(BQMS) は既存のデータウェアハウスを BigQuery へ移行するためのプロセスをサポートする機能を備えており、無料で利用できるツールセットです。Teradata のアセスメントと計画、10 以上の方言に対応した SQL 変換、データ転送、データ検証などの移行の各フェーズを支援するツールとなっています。
今回一般提供となったのは SQL の変換を行う以下の機能です。
- Batch SQL translator: Cloud Storage 上の異なる SQL 方言スクリプトを BigQuery が使用している標準 SQL へバッチ変換します
- Interactive SQL translator: Cloud Console 上で異なる SQL 方言を BigQuery が使用している標準 SQL へ変換します
Batch SQL translator
変換元となる SQL 方言は以下がサポートされています。変換はベストエフォートで行われ、成功するかは SQL 文の独自性と複雑性によって異なります。一部のスクリプトは手動で変換する必要がある場合があります。
- Amazon Redshift SQL
- Teradata SQL (SPLを除く)
- Apache HiveQL プレビュー
- Apache Spark SQL プレビュー
- Azure Synapse T-SQL プレビュー
- Basic Teradata Query (BTEQ) プレビュー
- IBM Netezza SQL/NZPLSQL プレビュー
- Oracle SQL, PL/SQL, Exadata プレビュー
- Snowflake SQL プレビュー
- Teradata SPL プレビュー
- Vertica SQL プレビュー
dwh-migration-dumper
ツールを使用してメタデータ情報を生成することができ、メタデータ情報によって変換の精度を向上させることもできます。dwh-migration-dumper
ツールは Apache 2 License となっており、Windows、macOS、Linux で実行できます。実行には Java 8 以降が必要となります。
また、バッチ変換中にオブジェクトの名称を変換する事もできます。
Interactive SQL translator
変換元となる SQL 方言は以下がサポートされています。
- Amazon Redshift SQL
- Teradata SQL (SPLを除く)
- Apache HiveQL プレビュー
- Apache Spark SQL プレビュー
- IBM Netezza SQL/NZPLSQL プレビュー
- Oracle SQL, PL/SQL, Exadata プレビュー
- Snowflake SQL プレビュー
- Teradata SPL プレビュー
Interactive SQL translator の制限事項として、入力したクエリのスキーマ情報を持っていません。正確な翻訳を行うためにはクエリで使用するテーブルの DDL をクエリの前に入力します。例えば以下のクエリの最初の 2 行のように DDL を記述します。
create table schema1.table1 (id int, field1 int, field2 varchar(16)); create table schema1.table2 (id int, field1 varchar(30), field2 date); select table1.field1, table2.field1 from table1, table2 where table1.id = table2.id;
やってみる
前提として、変換元は Amazon Redshift とし、対象データはTICKITを使用します。Redshift は事前に起動しておきます。起動時にサンプルデータを入れておくとそのまま検証を行えます。そうでない場合はドキュメントを元にデータセットをロードしてください。
クライアントは PostgreSQL クライアントと Google Cloud SDK がインストール済みとします。
変換元 SQL ファイルの準備
作業用ディレクトリを作成します。
$ mkdir tickitquery
TICKIT のテーブル定義をエクスポートします。
$ PGPASSWORD=<PASSWORD> psql -h <REDSHIFT_HOST_NAME> \ -U awsuser -d dev -p 5439 -t -A \ -c 'SHOW TABLE CATEGORY ;' >> ./tickitquery/Query.sql $ PGPASSWORD=<PASSWORD> psql -h <REDSHIFT_HOST_NAME> \ -U awsuser -d dev -p 5439 -t -A \ -c 'SHOW TABLE DATE ;' >> ./tickitquery/Query.sql $ PGPASSWORD=<PASSWORD> psql -h <REDSHIFT_HOST_NAME> \ -U awsuser -d dev -p 5439 -t -A \ -c 'SHOW TABLE EVENT ;' >> ./tickitquery/Query.sql $ PGPASSWORD=<PASSWORD> psql -h <REDSHIFT_HOST_NAME> \ -U awsuser -d dev -p 5439 -t -A \ -c 'SHOW TABLE VENUE ;' >> ./tickitquery/Query.sql $ PGPASSWORD=<PASSWORD> psql -h <REDSHIFT_HOST_NAME> \ -U awsuser -d dev -p 5439 -t -A \ -c 'SHOW TABLE USERS ;' >> ./tickitquery/Query.sql $ PGPASSWORD=<PASSWORD> psql -h <REDSHIFT_HOST_NAME> \ -U awsuser -d dev -p 5439 -t -A \ -c 'SHOW TABLE LISTING ;' >> ./tickitquery/Query.sql $ PGPASSWORD=<PASSWORD> psql -h <REDSHIFT_HOST_NAME> \ -U awsuser -d dev -p 5439 -t -A \ -c 'SHOW TABLE SALES ;' >> ./tickitquery/Query.sql
作成した./tickitquery/Query.sql
に以下の 5 個のクエリを追記します。
select sellerid, username, (firstname ||' '|| lastname) as name, city, sum(qtysold) from sales, date, users where sales.sellerid = users.userid and sales.dateid = date.dateid and year = 2008 and city = 'San Diego' group by sellerid, username, name, city order by 5 desc limit 5;
https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/c_sampledb.html
SELECT catgroup, SUM(qtysold) AS sold FROM category c, event e, sales s WHERE c.catid = e.catid AND e.eventid = s.eventid GROUP BY 1;
https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/materialized-view-auto-rewrite.html
select listagg(distinct sellerid, ', ') within group (order by sellerid) from sales where eventid = 4337;
https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_LISTAGG.html
select catgroup1, sold, unsold from (select catgroup, sum(qtysold) as sold from category c, event e, sales s where c.catid = e.catid and e.eventid = s.eventid group by catgroup) as a(catgroup1, sold) join (select catgroup, sum(numtickets)-sum(qtysold) as unsold from category c, event e, sales s, listing l where c.catid = e.catid and e.eventid = s.eventid and s.listid = l.listid group by catgroup) as b(catgroup2, unsold) on a.catgroup1 = b.catgroup2 order by 1;
https://docs.aws.amazon.com/redshift/latest/dg/r_Join_examples.html
with venuecopy as (select * from venue) select * from venuecopy order by 1 limit 10;
https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_WITH_clause.html
tickitquery
ディレクトリのファイルを Google Cloud Storage へアップロードします。
$ gsutil cp ./tickitquery/Query.sql gs://<GCS_BUCKET_NAME>
Batch SQL translator の実行
Cloud Console で BigQuery
ーSQL translation
を開きます。初めて使用する場合にはBigQuery Migration API
が有効になっていないため、ENABLE
をクリックして有効化します。
START TRANSLATION
をクリックします。
Display name
には変換ジョブの名称、Data location
はデータの配置先、Source dialect
は変換元のデータベースでここではRedshift
、Target dialect
は変換先で現状はBigQuery
のみ選択できます。入力後にNEXT
をクリックします。
Source location
にアップロードしたファイルのあるフォルダを指定してNEXT
をクリックします。
Target location
に変換したファイルの出力先フォルダを指定してNEXT
をクリックします。
最後にオプション設定がありますが、ここでは何も設定せずにCREATE
をクリックします。
変換が完了するまで少し待ちます。
変換が完了したら Target path をクリックして対象のパスの Cloud Storage へ遷移します。
Cloud Storage に以下のようなファイルが出力されています。
- query1.sql : 変換後の SQL ファイル
- batch_translation_report.csv : CSV 形式のバッチ変換を行った概要レポート(変換エラーがある場合に記載されます)
- consumed_name_map.json : JSON 形式の使用した出力名のマッピング
Batch SQL translator の結果確認
各々の内容を確認してみます。
CATEGORY テーブル
CREATE TABLE 文の内容は以下のような違いがあります。数値型は BigQuery ではINT64
になっています。BigQuery にもSMALLINT
型はありますが、INT64
のエイリアスなので一般的な整数型はINT64
型になるようです。文字列は長さの指定がなくなりSTRING
型に変換されています。Redshift と BigQuery ではアーキテクチャに違いがあり、データの分散を意識しないためオプションはなくなります。ソートキーはクラスタリングの列指定に変わっています。BigQuery では圧縮方法の選択が無いため Redshift の列圧縮はなくなります。
- smallint → INT64
- character varying(10) → STRING
- DISTSTYLE KEY → 無し
- distkey/SORTKEY → CLUSTER
- ENCODE lzo → 無し
CREATE TABLE public.category ( catid smallint NOT NULL ENCODE raw distkey, catgroup character varying(10) ENCODE lzo, catname character varying(10) ENCODE lzo, catdesc character varying(50) ENCODE lzo ) DISTSTYLE KEY SORTKEY ( catid );
CREATE TABLE __DEFAULT_DATABASE__.public.category ( catid INT64 NOT NULL, catgroup STRING, catname STRING, catdesc STRING ) CLUSTER BY catid;
DATE テーブル
CREATE TABLE 文の内容は以下のような違いがあります。CATEGORY テーブルと同様ですが、character
型はSTRING
型に変換されます。列の圧縮や非圧縮の指定はなくなります。
- smallint → INT64
- character → STRING
- DISTSTYLE KEY → 無し
- distkey/SORTKEY → CLUSTER
- ENCODE lzo/az64/raw → 無し
CREATE TABLE public.date ( dateid smallint NOT NULL ENCODE raw distkey, caldate date NOT NULL ENCODE az64, day character(3) NOT NULL ENCODE lzo, week smallint NOT NULL ENCODE az64, month character(5) NOT NULL ENCODE lzo, qtr character(5) NOT NULL ENCODE lzo, year smallint NOT NULL ENCODE az64, holiday boolean DEFAULT false ENCODE raw ) DISTSTYLE KEY SORTKEY ( dateid );
CREATE TABLE __DEFAULT_DATABASE__.public.date ( dateid INT64 NOT NULL, caldate DATE NOT NULL, day STRING NOT NULL, week INT64 NOT NULL, month STRING NOT NULL, qtr STRING NOT NULL, year INT64 NOT NULL, holiday BOOL ) CLUSTER BY dateid;
EVENT テーブル
CREATE TABLE 文の内容は以下のような違いがあります。他のテーブルと同様ですが、character
型はSTRING
型に変換されます。timestamp without time zone
はDATETIME
に変換されます。ソートキーだけでなく分散キーもクラスタリングの列指定となっています。
- smallint/integer → INT64
- character → STRING
- timestamp without time zone → DATETIME
- DISTSTYLE KEY → 無し
- distkey/SORTKEY → CLUSTER
- ENCODE lzo/az64 → 無し
CREATE TABLE public.event ( eventid integer NOT NULL ENCODE az64 distkey, venueid smallint NOT NULL ENCODE az64, catid smallint NOT NULL ENCODE az64, dateid smallint NOT NULL ENCODE raw, eventname character varying(200) ENCODE lzo, starttime timestamp without time zone ENCODE az64 ) DISTSTYLE KEY SORTKEY ( dateid );
CREATE TABLE __DEFAULT_DATABASE__.public.event ( eventid INT64 NOT NULL, venueid INT64 NOT NULL, catid INT64 NOT NULL, dateid INT64 NOT NULL, eventname STRING, starttime DATETIME ) CLUSTER BY eventid, dateid;
VENUE テーブル
CREATE TABLE 文の内容は以下のような違いがあります。他のテーブルと同様の変換となります。
- smallint/integer → INT64
- character → STRING
- DISTSTYLE KEY → 無し
- distkey/SORTKEY → CLUSTER
- ENCODE lzo/az64 → 無し
CREATE TABLE public.venue ( venueid smallint NOT NULL ENCODE raw distkey, venuename character varying(100) ENCODE lzo, venuecity character varying(30) ENCODE lzo, venuestate character(2) ENCODE lzo, venueseats integer ENCODE az64 ) DISTSTYLE KEY SORTKEY ( venueid );
CREATE TABLE __DEFAULT_DATABASE__.public.venue ( venueid INT64 NOT NULL, venuename STRING, venuecity STRING, venuestate STRING, venueseats INT64 ) CLUSTER BY venueid;
USERS テーブル
CREATE TABLE 文の内容は以下のような違いがあります。他のテーブルと同様の変換となりますが、boolean
型はBOOL
型に変換されます。
- integer → INT64
- character → STRING
- boolean → BOOL
- DISTSTYLE KEY → 無し
- distkey/SORTKEY → CLUSTER
- ENCODE lzo/az64 → 無し
CREATE TABLE public.users ( userid integer NOT NULL ENCODE raw distkey, username character(8) ENCODE lzo, firstname character varying(30) ENCODE lzo, lastname character varying(30) ENCODE lzo, city character varying(30) ENCODE lzo, state character(2) ENCODE lzo, email character varying(100) ENCODE lzo, phone character(14) ENCODE lzo, likesports boolean ENCODE raw, liketheatre boolean ENCODE raw, likeconcerts boolean ENCODE raw, likejazz boolean ENCODE raw, likeclassical boolean ENCODE raw, likeopera boolean ENCODE raw, likerock boolean ENCODE raw, likevegas boolean ENCODE raw, likebroadway boolean ENCODE raw, likemusicals boolean ENCODE raw ) DISTSTYLE KEY SORTKEY ( userid );
CREATE TABLE __DEFAULT_DATABASE__.public.users ( userid INT64 NOT NULL, username STRING, firstname STRING, lastname STRING, city STRING, state STRING, email STRING, phone STRING, likesports BOOL, liketheatre BOOL, likeconcerts BOOL, likejazz BOOL, likeclassical BOOL, likeopera BOOL, likerock BOOL, likevegas BOOL, likebroadway BOOL, likemusicals BOOL ) CLUSTER BY userid;
LISTING テーブル
CREATE TABLE 文の内容は以下のような違いがあります。他のテーブルと同様の変換となりますが、numeric(8,2)
型がNUMERIC
型に変換されます。Redshift では(8,2)
の精度が定義されていましたが、変換後は精度が定義されていません。
- integer/smallint → INT64
- numeric(8,2) → NUMERIC
- timestamp without time zone → DATETIME
- DISTSTYLE KEY → 無し
- distkey/SORTKEY → CLUSTER
- ENCODE az64/raw → 無し
CREATE TABLE public.listing ( listid integer NOT NULL ENCODE az64 distkey, sellerid integer NOT NULL ENCODE az64, eventid integer NOT NULL ENCODE az64, dateid smallint NOT NULL ENCODE raw, numtickets smallint NOT NULL ENCODE az64, priceperticket numeric(8,2) ENCODE az64, totalprice numeric(8,2) ENCODE az64, listtime timestamp without time zone ENCODE az64 ) DISTSTYLE KEY SORTKEY ( dateid );
CREATE TABLE __DEFAULT_DATABASE__.public.listing ( listid INT64 NOT NULL, sellerid INT64 NOT NULL, eventid INT64 NOT NULL, dateid INT64 NOT NULL, numtickets INT64 NOT NULL, priceperticket NUMERIC, totalprice NUMERIC, listtime DATETIME ) CLUSTER BY listid, dateid;
SALES テーブル
CREATE TABLE 文の内容は以下のような違いがあります。他のテーブルと同様の変換となります。
- integer/smallint → INT64
- numeric(8,2) → NUMERIC
- timestamp without time zone → DATETIME
- DISTSTYLE KEY → 無し
- distkey/SORTKEY → CLUSTER
- ENCODE az64 → 無し
CREATE TABLE public.sales ( salesid integer NOT NULL ENCODE az64, listid integer NOT NULL ENCODE az64 distkey, sellerid integer NOT NULL ENCODE az64, buyerid integer NOT NULL ENCODE az64, eventid integer NOT NULL ENCODE az64, dateid smallint NOT NULL ENCODE raw, qtysold smallint NOT NULL ENCODE az64, pricepaid numeric(8,2) ENCODE az64, commission numeric(8,2) ENCODE az64, saletime timestamp without time zone ENCODE az64 ) DISTSTYLE KEY SORTKEY ( dateid );
CREATE TABLE __DEFAULT_DATABASE__.public.sales ( salesid INT64 NOT NULL, listid INT64 NOT NULL, sellerid INT64 NOT NULL, buyerid INT64 NOT NULL, eventid INT64 NOT NULL, dateid INT64 NOT NULL, qtysold INT64 NOT NULL, pricepaid NUMERIC, commission NUMERIC, saletime DATETIME ) CLUSTER BY listid, dateid;
Select 文
文字列連結をconcat
に変換、複数テーブルの結合で明示的にCROSS JOIN
を記述、ORDER BY
にNULLS FIRST
を記述する点などがポイントです。
SELECT sellerid ,username ,(firstname ||' '|| lastname) AS name ,city ,SUM(qtysold) FROM sales, date, users WHERE sales.sellerid = users.userid AND sales.dateid = date.dateid AND year = 2008 AND city = 'San Diego' GROUP BY sellerid ,username ,name ,city ORDER BY 5 desc LIMIT 5;
SELECT sales.sellerid ,users.username ,concat(users.firstname,' ',users.lastname) AS name ,users.city ,SUM(sales.qtysold) AS sum FROM __DEFAULT_DATABASE__.public.sales CROSS JOIN __DEFAULT_DATABASE__.public.date CROSS JOIN __DEFAULT_DATABASE__.public.users WHERE sales.sellerid = users.userid AND sales.dateid = date.dateid AND date.year = 2008 AND users.city = 'San Diego' GROUP BY 1 ,2 ,3 ,4 ORDER BY 5 DESC NULLS FIRST LIMIT 5;
複数テーブルの結合で明示的にCROSS JOIN
を記述する点などがポイントです。
SELECT catgroup ,SUM(qtysold) AS sold FROM category c, event e, sales s WHERE c.catid = e.catid AND e.eventid = s.eventid GROUP BY 1;
SELECT c.catgroup ,SUM(s.qtysold) AS sold FROM __DEFAULT_DATABASE__.public.category AS c CROSS JOIN __DEFAULT_DATABASE__.public.event AS e CROSS JOIN __DEFAULT_DATABASE__.public.sales AS s WHERE c.catid = e.catid AND e.eventid = s.eventid GROUP BY 1;
項目の連結をlistagg
からstring_agg
に変換、sales.sellerid
を明示的に文字列へキャストする点などがポイントです。
SELECT listagg(distinct sellerid,',') within group (order by sellerid) FROM sales WHERE eventid = 4337;
SELECT string_agg(DISTINCT CAST(sales.sellerid AS STRING),',' ORDER BY sales.sellerid) AS string_agg FROM __DEFAULT_DATABASE__.public.sales WHERE sales.eventid = 4337;
複数テーブルの結合で明示的にCROSS JOIN
を記述、JOIN
を明示的にINNER JOIN
と記述、ORDER BY
にNULLS LAST
を記述する点などがポイントです。
SELECT catgroup1 ,sold ,unsold FROM ( SELECT catgroup ,SUM(qtysold) AS sold FROM category c, event e, sales s WHERE c.catid = e.catid AND e.eventid = s.eventid GROUP BY catgroup ) AS a(catgroup1, sold) JOIN ( SELECT catgroup ,SUM(numtickets)-SUM(qtysold) AS unsold FROM category c, event e, sales s, listing l WHERE c.catid = e.catid AND e.eventid = s.eventid AND s.listid = l.listid GROUP BY catgroup ) AS b(catgroup2, unsold) ON a.catgroup1 = b.catgroup2 ORDER BY 1;
SELECT a.catgroup1 ,a.sold ,b.unsold FROM ( SELECT c.catgroup AS catgroup1 ,SUM(s.qtysold) AS sold FROM __DEFAULT_DATABASE__.public.category AS c CROSS JOIN __DEFAULT_DATABASE__.public.event AS e CROSS JOIN __DEFAULT_DATABASE__.public.sales AS s WHERE c.catid = e.catid AND e.eventid = s.eventid GROUP BY 1 ) AS a INNER JOIN ( SELECT c_0.catgroup AS catgroup2 ,SUM(l.numtickets) - SUM(s_0.qtysold) AS unsold FROM __DEFAULT_DATABASE__.public.category AS c_0 CROSS JOIN __DEFAULT_DATABASE__.public.event AS e_0 CROSS JOIN __DEFAULT_DATABASE__.public.sales AS s_0 CROSS JOIN __DEFAULT_DATABASE__.public.listing AS l WHERE c_0.catid = e_0.catid AND e_0.eventid = s_0.eventid AND s_0.listid = l.listid GROUP BY 1 ) AS b ON a.catgroup1 = b.catgroup2 ORDER BY 1 NULLS LAST;
ORDER BY
にNULLS LAST
を記述する点などがポイントです。
WITH venuecopy AS ( SELECT * FROM venue ) SELECT * FROM venuecopy ORDER BY 1 LIMIT 10;
WITH venuecopy AS ( SELECT venue.* FROM __DEFAULT_DATABASE__.public.venue ) SELECT venuecopy.* FROM venuecopy ORDER BY 1 NULLS LAST LIMIT 10;
テーブル定義を含めて変換しているので、概ね妥当な書き換えとなっているように見受けられます。興味深い点としては、以下のような傾向があることかと思います。
- 列定義の文字列の長さや精度の記述がなくなる
- 暗黙の型変換を行わず、明示的にキャストする
CROSS JOIN
やINNER JOIN
を明示的に記述する
Interactive SQL translator の実行
次に Interactive SQL translator を使用してみます。こちらはCloud Console 上で対話的に変換できます。
Cloud Console で BigQuery の SQL Workspace を開きます。MORE
からEnable SQL translation
をクリックします。
Cloud Console で BigQuery の SQL Workspace を開きます。MORE
からEnable SQL translation
をクリックします。
次にMORE
からTranslation settings
をクリックします。
Source diflect
でRedshift
を選択して、Processing Locationで実行するリージョンを選択します。そしてSAVE
をクリックします。
Redshift から BigQuery の標準 SQL への変換を行う設定となりました。ここからは左のペインに Redshift のクエリを入力していきます。
バッチ変換で使用した Select3 のクエリを入力して、TRANSLATE
をクリックして変換します。
SELECT listagg(distinct sellerid,',') within group (order by sellerid) FROM sales WHERE eventid = 4337;
以下のように表示されます。しかしバッチ変換と異なりsales.sellerid
をSTRING
へキャストしていません。これはテーブル定義が入力されていないためです。
SELECT string_agg(DISTINCT sales.sellerid, ',' ORDER BY sales.sellerid) AS string_agg FROM sales WHERE sales.eventid = 4337 ;
Select3 の前に SALES テーブルの DDL を記述して変換してみます。
以下の Select 文となりバッチ変換と同様にsales.sellerid
をSTRING
型にキャストしています。発生しているエラーは BigQuery 上にpublic
というデータセットが無いという真っ当な物なので特に問題ありません。
SELECT string_agg(DISTINCT CAST(sales.sellerid as STRING), ',' ORDER BY sales.sellerid) AS string_agg FROM public.sales WHERE sales.eventid = 4337 ;
さいごに
BigQuery は Google Cloud の中でも特に人気の高いサービスであり、DWH をオンプレミスや他のサービスから移行する先のサービスとしてもよく使用されます。しかし、移行するためにはテーブル定義やクエリを変更する必要があるため、大きな時間とコストを使い、かつリスクもあるものでした。BigQuery Migration Service を使用してその時間とコストを低減しつつ安全に移行を行うことが可能になってきました。今後の BigQuery への移行作業では必須の機能と思われますので、ぜひ使用していきたいと思います。